<html>
<head>
  <title>15.解释器（Interpreter）模式</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602628 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="23794"/>
<h1>15.解释器（Interpreter）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/8/3 7:17</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2021/3/11 0:15</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-weight: bold;">一、基本描述</span></div><ul><li><div>给定一个语言，定义它的文法的一种表示，并定义一个解释器，这个解释器使用该表示来解释句子。</div></li><li><div>这里的语言是指使用规定格式和语法的代码。</div></li><li><div>如果一种特定类型的问题发生的频率足够高，那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器，该解释器通过解释这些句子来解决该问题。</div></li><li><div>当有一个语言需要解释执行，并且可将该语言中的句子表示为一个抽象语法树时，可使用解释器模式。而当存在以下情况时该模式效果最好：</div></li></ul><div style="margin-left: 40px;">1）文法简单。对于复杂的文法，文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是最好的选择，它们无须构建抽象语法树即可解释表达式，这样可以节省空间而且还可能节省时间。</div><div style="margin-left: 40px;">2）效率不是关键问题。最高效的解释器通常不是通过直接解释语法分析树实现的，而是首先将它们转换成另一种形式。例如，正则表达式通常被转换成状态机。但即使在这种情况下，转换器也可用解释器模式实现，该模式仍是有用的。</div><div style="margin-left: 40px;">3） 重复发生的问题可以使用解释器模式。</div><ul><li><div>解释器模式并未解释如何创建一个抽象语法树，换言之，它不涉及语法分析。</div></li><li><div>解释器模式需要解决的是，如果一种特定的类型的问题发生的频率足够的高，那么可能就值得将该问题的各个实例表述为一个简单语言中的句子，这样就可以构建一个解释器，该解释器通过解释这些句子来解决该问题。</div></li><li><div>解释器模式就是用“迷你语言”来表现程序要解决的问题，以迷你语言写成“迷你程序”来表现具体的问题。</div></li><li><div>解释器模式是一种按照规定语法进行解析的方案， 在现在项目中使用较少。</div></li><li><div>尽量不要在重要的模块中使用解释器模式， 否则维护会是一个很大的问题。 在项目中可以使用shell、 JRuby、 Groovy等脚本语言来代替解释器模式， 弥补Java编译型语言的不足。</div></li><li><div>解释器模式描述了如何为简单的语言定义一个文法， 如何在该语言中表示一个句子， 以及如何解释这些句子。</div></li><li><div>解释器模式的先置技术：</div></li></ul><div style="margin-left: 40px;">1）表示一个语言的文法规则；</div><div style="margin-left: 40px;">2）构造一颗抽象语法树。</div><ul><li><div>解释器模式是一种使用频率相对较低但学习难度较大的设计模式， 它用于描述如何使用面向对象语言构成一个简单的语言解释器。</div></li><li><div>在某些情况下， 为了更好地描述某一些特定类型的问题， 我们可以创建一种新的语言， 这种语言拥有自己的表达式和结构， 即文法规则， 这些问题的实例将对应为该语言中的句子。 此时， 可以使用解释器模式来设计这种新的语言。</div></li><li><div>使用解释器模式为语言创建解释器：</div></li><ul><li><div>当你需要实现一个简单的语言时，就使用解释器模式定义语法的类，并用一个解释器解释句子。每个语法规则都用一个类代表。</div></li><li><div>要想解释这种语言，就调用每个表达式类型的 interpret() 方法。此方法需要传入一个上下文（ Context）——也就是我们正在解析的语言字符串输入流——然后进行比对并采取适当的动作。</div></li></ul><li><div>用途</div></li><ul><li><div>当你需要实现一个简单的语言时，使用解释器。</div></li><li><div>当你有一个简单的语法，而且简单比效率更重要时，使用解释器。</div></li><li><div>可以处理脚本语言和编程语言</div></li></ul></ul><div><span style="font-weight: bold;">二、图例</span></div><div><img src="15.解释器（Interpreter）模式_files/Image.png" type="image/png" data-filename="Image.png"/></div><ul><li><div>AbstractExpression：抽象表达式，声明一个抽象的解释操作，这个接口为抽象语法树中所有的节点所共享。抽象表达式是生成语法集合（也叫做语法树） 的关键， 每个语法集合完成指定语法解任务， 它是通过递归调用的方式， 最终由最小的语法单元进行解析完成。具体解释任务由各个实现类来完成。</div></li><li><div>TerminalExpression：终结符表达式，实现与文法中的终结符相关联的解释操作，一个句子中的每个终结符需要改类的一个实例。 通常， 终结符表达式比较简单， 主要是处理场景元素和数据的转换。终结符是语言的最小组成单位，不能再进行拆分。实现抽象表达式中所要求的接口，文法中每一个终结符都有一个具体的终结符表达式与之对应。通常，终结符表达式类比较简单，主要处理场景元素和数据的转换。通常终结符只有一个类，但是有多个对象。</div></li><li><div>NonterminalExpression：非终结符表达式，对文法中的每一条规则 R ::= R1R2...Rn 都需要一个NonterminalExpression类。为从R1到Rn的每个符号都维护一个AbstractExpression类型的实例变量。为文法中的非终结符实现解释操作，解释一般要递归的调用表示R1到Rn的那些对象的解释操作。每个非终结符表达式都代表了一个文法规则， 并且每个文法规则都只关心自己周边的文法规则的结果（注意是结果） ， 因此这就产生了每个非终结符表达式调用自己周边的非终结符表达式， 然后最终、 最小的文法规则就是终结符表达式， 终结符表达式的概念就是如此，不能够再参与比自己更小的文法运算了。非终结符都是一个完整的句子，包含一系列终结符与非终结符，可以进一步分解。每个非终结符表达式都会对其他表达式产生依赖。非终结符表达式根据逻辑的复杂程度而增加，原则上每一个规则都对应一个非终结符表达式。由于非终结符表达式中可以包含终结符表达式，也可以继续包含非终结符表达式。</div></li><li><div>Context：上下文、环境类，包含解释器之外的一些全局信息。 在解释器模式中， 环境类Context用于存储解释器之外的一些全局信息， 它通常作为参数被传递到所有表达式的解释方法interpret()中， 可以在Context对象中存储和访问表达式解释器的状态， 向表达式解释器提供一些全局的、 公共的数据， 此外还可以在Context中增加一些所有表达式解释器都共有的功能， 减轻解释器的职责。可能存储了需要解释的语句。通常在Context中包含了一个HashMap或ArrayList等类型的集合对象（也可直接由HashMap等集合类充当环境类），存储一系列公共信息，用于在具体的解释操作时从中获取相关信息，存储一系列公共信息，用于在具体的解释操作时，从中获取相关信息，当系统无需提供全局公共信息时，可以省略环境类，可根据实际情况决定是否需要环境类。</div></li><li><div>Client：客户，构建（或被给定）表示该文法定义的语言中一个特定的句子的抽象语法树。该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成。调用解释操作。通常Client是一个封装类，封装的结果就是传递进来一个规范语法文件，解析器分析后产生结果并返回，避免了调用者与语法解释器的耦合关系，</div></li><li><div>协作：</div></li></ul><div style="margin-left: 40px;">1）Client构建（或被给定）一个句子，它是NonterminalExpression和TerminalExpression的实例的一个抽象语法树，然后初始化上下文并调用解释操作。</div><div style="margin-left: 40px;">2）每一个非终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解释操作构成了递归的基础。</div><div style="margin-left: 40px;">3）每一个节点的解释操作作用上下文来存储和访问解释器的状态。</div><div><span style="font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div>易于改变和扩展文法。因为该模式使用类来表示文法规则，你可以使用继承来改变或扩展该文法。已有的表达式可被增量式的改变，而新的表达式可定义为旧表达式的变体。</div></li><li><div>易于实现文法。定义抽象语法树中各个节点的类的实现大体相似，这些类易于直接编写，通常他们也可用一个编译器或语法分析程序生成器自动生成。</div></li><li><div>增加了新的解表达式的方式。解释器模式使得实现新表达式“计算”变得容易。</div></li><li><div>将每一个语法规则表示成一个类，方便于实现语言。</div></li><li><div>因为语法由许多类表示，所以你可以轻易地改变或扩展此语言。</div></li><li><div>通过在类结构中加入新的方法，可以在解释的同时增加新的行为，例如打印格式的美化或者进行复杂的程序验证。</div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div>复杂的文法难以维护。解释器模式为文法中的每一条规则至少定义了一个类（使用BNF定义的文法规则需要更多的类），因此包含许多规则的文法可能难以管理与维护。可应用其他的设计模式来缓解这一问题，但当文法非常复杂时，其他的技术如语法分析程序或编译器生成器更为合适。</div></li><li><div>解释器模式采用递归调用方法。 每个非终结符表达式只关心与自己有关的表达式， 每个表达式需要知道最终的结果， 必须一层一层地剥茧， 无论是面向过程的语言还是面向对象的语言， 递归都是在必要条件下使用的， 它导致调试非常复杂。 想想看， 如果要排查一个语法错误， 我们是不是要一个断点一个断点地调试下去， 直到最小的语法单元。</div></li><li><div>效率问题。 解释器模式由于使用了大量的循环和递归， 效率是一个不容忽视的问题， 特别是一用于解析复杂、 冗长的语法时， 效率是难以忍受的。</div></li><li><div>当语法规则的数目太大时，这个模式可能会变得非常繁杂。在这种情况下，使用解析器/编译器的产生器可能更合适。</div></li></ul><div><span style="font-weight: bold;">四、可能的改进</span></div><ul><li><div>与Flyweight模式联用：说明如何在抽象语法树中共享终结符。</div></li><li><div>与Iterator模式联用：解释器可用一个迭代器遍历该结构。</div></li><li><div>与Visitor模式联用：可用来在一个类中维护抽象语法树中的各个节点的行为。</div></li></ul><div><span style="font-weight: bold;">五、个人理解</span></div><ul><li><div>解释器模式在实际当中应用并不广泛。基本上能够使用解释器模式的场景都会有替代的工具，这些工具都是针对特定场景优化后形成的，并不需要解释器模式这样单独开发。</div></li><li><div>解释器模式干两部分的活：语言的表示和表示的解释。也即语言的文法和语句的语义分析都是由解释器完成，但是词法分析、语法分析从而构造一颗抽象语法树的过程却并不是解释器完成，怪不得解释器模式在实际中并不常用。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br/></div></span>
</div></body></html> 